package addition.nio;

import addition.utils.FileUtils;
import org.junit.Test;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class ChannelDemo {

    /*
     * Channel 类型
     *  FileChannel
     *  SocketChannel
     *  ServerSocketChannel
     *  DatagramChannel
     *
     * 获取 Channel 实例
     *  getChannel() 方法
     *  static open() 方法
     *
     * transferTo() / transferFrom() (直接缓冲)
     * 最大传输文件数据为 2G
     *
     * 分散（Scatter）读取（Scattering Read） / 聚集（Gather）写入（Gathering Write）
     *
     */

    // 非直接缓冲方式内存拷贝
    @Test
    public void test1() throws IOException {
        long start = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream(FileUtils.getRootFilePath("wallhaven-z85gyw.jpg"));
        FileOutputStream fos = new FileOutputStream("wallhaven-z85gyw-copy.jpg");

        // 通过 getChannel() 获取 FileChannel
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();

        // 创建 Buffer
        ByteBuffer buf = ByteBuffer.allocate(1024);
        // 写入数据
        while (inChannel.read(buf) != -1) {
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }

        inChannel.close();
        outChannel.close();
        fis.close();
        fos.close();

        long end = System.currentTimeMillis();
        System.out.println("Time: " + (end - start));  // Time: 60
    }

    // 内存映射文件方式文件拷贝（直接缓冲）
    @Test
    public void test2() throws IOException {
        long start = System.currentTimeMillis();
        FileChannel inChannel = FileChannel.open(Paths.get(FileUtils.getRootFilePath("wallhaven-z85gyw.jpg")),
                StandardOpenOption.READ);
        FileChannel outChanel = FileChannel.open(Paths.get("wallhaven-copy.jpg"),
                StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        // 通过内存映射文件方式（直接缓冲）
        MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChanel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

        byte[] buffer = new byte[inMappedBuf.capacity()];
        inMappedBuf.get(buffer);
        outMappedBuf.put(buffer);

        inChannel.close();
        outChanel.close();

        long end = System.currentTimeMillis();
        System.out.println("Time: " + (end - start));  // Time: 10
    }

    // transferTo() / transferFrom() (直接缓冲)
    @Test
    public void test3() throws IOException {
        RandomAccessFile inFile = new RandomAccessFile(FileUtils.getRootFilePath("wallhaven-z85gyw.jpg"), "rw");
        RandomAccessFile outFile = new RandomAccessFile("wallhaven-copy.jpg", "rw");

        FileChannel inChannel = inFile.getChannel();
        FileChannel outChannel = outFile.getChannel();

        // inChannel.transferTo(0, inChannel.size(), outChannel);
        outChannel.transferFrom(inChannel, 0, inChannel.size());

        inChannel.close();
        outChannel.close();
    }

    // 分散读取 / 聚集写入
    @Test
    public void test4() throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile(FileUtils.getRootFilePath("text.txt"), "rw");
        RandomAccessFile raf2 = new RandomAccessFile("text-copy.txt", "rw");

        FileChannel channel1 = raf1.getChannel();
        FileChannel channel2 = raf2.getChannel();

        ByteBuffer bb1 = ByteBuffer.allocate(1024);
        ByteBuffer bb2 = ByteBuffer.allocate(1024 * 4);
        ByteBuffer bb3 = ByteBuffer.allocate(1024 * 8);

        // 分散读取
        channel1.read(new ByteBuffer[]{bb1, bb2, bb3});

        // 输出读取内容
        bb1.flip();
        byte[] buffer = new byte[bb1.limit()];
        bb1.get(buffer, 0, bb1.limit());
        System.out.println(new String(buffer, 0, buffer.length));

        // 聚集写入
        bb1.rewind();
        bb2.flip();
        bb3.flip();
        channel2.write(new ByteBuffer[]{bb1, bb2, bb3});

        channel1.close();
        channel2.close();
        raf1.close();
        raf2.close();
    }

}
